# Copyright 2010 Mikko Rantanen
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import fuse
import stat
import os
import sys
import logging
import traceback
import simplejson as json
from StringIO import StringIO
from time import time

class ChannelFile(fuse.Stat):
    
    def __init__(self, name, destroy_cb = None, message_cb = None):
        logging.info("Communication channel %s constructed" % name)

        self.name = name
        self.destroy_cb = destroy_cb
        self.message_cb = message_cb

        self.st_nlink = 1
        self.st_size = 0
        self.st_mode = stat.S_IFREG | 0777

        self.st_ino = 0
        self.st_dev = 0
        self.st_uid = os.getuid()
        self.st_gid = os.getgid()

        self.st_atime = int(time())
        self.st_mtime = self.st_atime
        self.st_ctime = self.st_atime

        self.inbuf = StringIO()
        self.outbuf = StringIO()
        self.outlines = []
        self.refcount = 0

    def open(self, flags):
        logging.info("Open")
        self.refcount += 1

    def read(self, size, offset):
        logging.info("Reading %s bytes from offset %s" % (size, offset))
        self.outbuf.seek(offset)
        return self.outbuf.read(size)

    def write(self, buf, offset):
        logging.info("Writing %s to offset %s" % (buf, offset))

        lines = buf.split('\n')

        # If there is a single newline, append the start
        # to the last remainder and process it.
        if len(lines) > 1:
            self.inbuf.write(lines[0])
            self.process(self.inbuf.getvalue())
            self.inbuf = StringIO()

        # If there are fully contained strings, process them
        # as they are.
        for i in range(1, len(lines)-1):
            self.process(lines[i])

        # Append the last segment to the current remainder
        self.inbuf.write(lines[-1])

        self.st_atime = int(time())
        self.st_mtime = self.st_atime

        return len(buf)

    def release(self, flags):
        logging.info("Releasing channel")
        assert self.refcount > 0, "There are no open references"

        self.refcount -= 1
        if self.refcount == 0 and self.destroy_cb:
            self.destroy_cb(self)
        return 0

    # TODO: Multithread this.
    def process(self, msg):
        cmd_sep = msg.find(',')
        id_sep = msg.find(',', cmd_sep+1)

        assert cmd_sep > 0 and id_sep > cmd_sep + 1, "Malformed message"
        cmd = msg[:cmd_sep]
        msg_id = msg[cmd_sep+1:id_sep]
        arg = json.loads(msg[id_sep+1:])

        result = None
        status = "success"
    
        try:
            result = self.message_cb(cmd, arg)
        except:
            logging.error(traceback.format_exc())
            status = "error"
            exc = sys.exc_info()
            result = [
                exc[0].__name__,
                traceback.format_exception_only(exc[0], exc[1]),
                traceback.extract_tb(exc[2])
            ]
            del exc
            
        line = "%s,%s,%s\n" % (msg_id, status, json.dumps(result))
        self.outbuf.write(line)
        self.st_size = self.outbuf.len

